﻿using System;
using System.Collections.Generic;
using System.Threading;
using Interactive.Utility.Extension;
using log4net;

namespace Interactive.Utility.Cache {
    public class MemCacheProxy<V> : ICacheProxy<V> {
        /// <summary>
        /// memcache锁的前缀
        /// </summary>
        public const string MenCacheLockPre = "YJMC_";

        private static readonly ILog log = LogManager.GetLogger("MemCacheProxy<T>");

        /// <summary>
        /// 缓存锁的键
        /// </summary>
        private readonly string cacheLock;

        /// <summary>
        /// memcache删除的委托
        /// </summary>
        private readonly Func<string, bool> deleteMem;

        /// <summary>
        /// 获取缓存的方法
        /// </summary>
        private readonly Func<V> getFun;

        /// <summary>
        /// memcache获取的委托
        /// </summary>
        private readonly Func<string, object> getMem;

        /// <summary>
        /// 获取时间间隔,默认不跟新
        /// </summary>
        private readonly TimeSpan getSpan = TimeSpan.Zero;

        /// <summary>
        /// 锁
        /// </summary>
        private readonly object myLock = new object();

        /// <summary>
        /// 前缀,当做key
        /// </summary>
        private readonly string prefix;

        /// <summary>
        /// memcache写的委托
        /// </summary>
        private readonly Func<string, object, DateTime, bool> setMen;

        private readonly Int16 waitMillseconds = 50;

        /// <summary>
        /// 用来记录上次获取的时间
        /// </summary>
        private DateTime lastGetTime = DateTime.MinValue;

        public MemCacheProxy(TimeSpan getSpan, Int16 waitMillseconds, string prefix, Func<V> getFun,
                             Func<string, object> getMem, Func<string, object, DateTime, bool> setMen,
                             Func<string, bool> deleteMem){
            if (getSpan.TotalMilliseconds < 0)
                throw new ArgumentException("获取时间间隔不能为负数!", "getSpan");
            if (getFun == null)
                throw new ArgumentException("获取值的方法不能为空!", "getFun");
            if (string.IsNullOrEmpty(prefix))
                throw new ArgumentException("前缀一定要加上喔!", "prefix");


            this.getSpan = getSpan;
            this.getFun = getFun;
            this.getMem = getMem;
            this.setMen = setMen;
            this.deleteMem = deleteMem;
            this.prefix = prefix;
            cacheLock = MenCacheLockPre + prefix;

            this.waitMillseconds = waitMillseconds;
        }

        #region Implementation of ICacheProxy<T>

        /// <summary>
        /// 获取值
        /// </summary>
        public V Value{
            get{
                DateTime now = DateTime.Now;
                object cacheLockObj;
                if (now > lastGetTime &&
                    ((cacheLockObj = getMem(cacheLock)) == null || cacheLockObj.ToString() == "0")){
                    if (Monitor.TryEnter(myLock, waitMillseconds)){
                        {
                            try{
                                if (now > lastGetTime &&
                                    ((cacheLockObj = getMem(cacheLock)) == null || cacheLockObj.ToString() == "0")){
                                    lastGetTime = now.Add(getSpan);
                                    setMen(cacheLock, "1", DateTime.MaxValue);
                                    try{
                                        V temp = getFun();
                                        setMen(prefix, temp, now.AddDays(2));
                                        return temp;
                                    }
                                    catch (Exception exception){
                                        log.Error("在更新缓存值时出错!", exception);
                                    }
                                    finally{
                                        setMen(cacheLock, "0", DateTime.MaxValue);
                                    }
                                }
                            }
                            finally{
                                Monitor.Exit(myLock);
                            }
                        }
                    }
                }
                return getMem(prefix).AsTUnSafe<V>();
            }
        }

        /// <summary>
        /// 强制更新缓存,无限等待
        /// </summary>
        /// <returns></returns>
        public bool EnforceRefresh(){
            return EnforceRefresh(Timeout.Infinite);
        }

        /// <summary>
        /// 强制更新缓存
        /// </summary>
        /// <param name="millseconds">等待的毫秒数</param>
        /// <returns></returns>
        public bool EnforceRefresh(int millseconds){
            if (Monitor.TryEnter(myLock, millseconds)){
                try{
                    lastGetTime = DateTime.MinValue;
                    deleteMem(prefix);
                    return true;
                }
                finally{
                    Monitor.Exit(myLock);
                }
            }
            return false;
        }

        #endregion
    }

    public class MemCacheProxy<K, V> : ICacheProxy<K, V>
    {
        private static readonly ILog log = LogManager.GetLogger("MemCacheProxy<K, V>");

        /// <summary>
        /// memcache删除的委托
        /// </summary>
        private readonly Func<string, bool> deleteMem;

        /// <summary>
        /// 内部容器
        /// </summary>
        private readonly IDictionary<K, MemCacheWrapper> dictionary;

        /// <summary>
        /// 获取缓存的方法
        /// </summary>
        private readonly Func<K, V> getFun;

        /// <summary>
        /// memcache获取的委托
        /// </summary>
        private readonly Func<string, object> getMem;

        /// <summary>
        /// 获取时间间隔,默认不跟新
        /// </summary>
        private readonly TimeSpan getSpan = TimeSpan.Zero;

        /// <summary>
        /// 锁
        /// </summary>
        private readonly object myLock = new object();

        /// <summary>
        /// 前缀,当做key
        /// </summary>
        private readonly string prefix;

        /// <summary>
        /// memcache写的委托
        /// </summary>
        private readonly Func<string, object, DateTime, bool> setMen;

        private readonly Int16 waitMillseconds = 50;


        public MemCacheProxy(TimeSpan getSpan, Int16 waitMillseconds, string prefix, Func<K, V> getFun,
                             Func<string, object> getMem, Func<string, object, DateTime, bool> setMen,
                             Func<string, bool> deleteMem, int capacity){
            if (getSpan.TotalMilliseconds < 0)
                throw new ArgumentException("获取时间间隔不能为负数!", "getSpan");
            if (getFun == null)
                throw new ArgumentException("获取值的方法不能为空!", "getFun");
            if (string.IsNullOrEmpty(prefix))
                throw new ArgumentException("前缀一定要加上喔!", "prefix");


            this.getSpan = getSpan;
            this.getFun = getFun;
            this.getMem = getMem;
            this.setMen = setMen;
            this.deleteMem = deleteMem;
            this.prefix = prefix;
            dictionary = new Dictionary<K, MemCacheWrapper>(capacity);

            this.waitMillseconds = waitMillseconds;
        }

        #region Implementation of ICacheProxy<T,V>

        /// <summary>
        /// 获取值
        /// </summary>
        public V GetValue(K key){
            MemCacheWrapper cacheWrapper;
            if (!dictionary.TryGetValue(key, out cacheWrapper)){
                lock (myLock){
                    if (!dictionary.TryGetValue(key, out cacheWrapper)){
                        cacheWrapper = new MemCacheWrapper(this);
                        dictionary.Add(key, cacheWrapper);
                    }
                }
            }
            return cacheWrapper.GetValue(key);
        }

        /// <summary>
        /// 强制更新缓存,无限期等待
        /// </summary>
        /// <param name="key">键</param>
        /// <returns>结果</returns>
        public bool EnforceRefresh(K key){
            return EnforceRefresh(key, Timeout.Infinite);
        }

        /// <summary>
        /// 强制更新缓存
        /// </summary>
        /// <param name="key">键</param>
        /// <param name="millseconds">等待的毫秒数</param>
        /// <returns></returns>
        public bool EnforceRefresh(K key, int millseconds){
            MemCacheWrapper cacheWrapper;
            if (dictionary.TryGetValue(key, out cacheWrapper)) return cacheWrapper.EnforceRefresh(millseconds, key);
            return true;
        }


        private class MemCacheWrapper {
            private readonly string cacheLock;
            private readonly MemCacheProxy<K, V> cacheProxy;

            /// <summary>
            /// 锁
            /// </summary>
            private readonly object myLock = new object();


            /// <summary>
            /// 用来记录上次获取的时间
            /// </summary>
            private DateTime lastGetTime = DateTime.MinValue;

            public MemCacheWrapper(MemCacheProxy<K, V> cacheProxy){
                this.cacheProxy = cacheProxy;
                cacheLock = MemCacheProxy<K>.MenCacheLockPre + cacheProxy.prefix + "_";
            }


            /// <summary>
            /// 获取值
            /// </summary>
            public V GetValue(K key){
                DateTime now = DateTime.Now;
                object cacheLockObj;
                if (now > lastGetTime &&
                    ((cacheLockObj = cacheProxy.getMem(cacheLock + key)) == null || cacheLockObj.ToString() == "0")){
                    if (Monitor.TryEnter(myLock, cacheProxy.waitMillseconds)){
                        {
                            try{
                                if (now > lastGetTime &&
                                    ((cacheLockObj = cacheProxy.getMem(cacheLock + key)) == null ||
                                     cacheLockObj.ToString() == "0")){
                                    lastGetTime = now.Add(cacheProxy.getSpan);
                                    cacheProxy.setMen(cacheLock + key, "1", DateTime.MaxValue);
                                    try{
                                        V temp = cacheProxy.getFun(key);
                                        cacheProxy.setMen(cacheProxy.prefix + key, temp, now.AddDays(2));
                                        return temp;
                                    }
                                    catch (Exception exception){
                                        log.Error("在更新缓存值时出错!", exception);
                                    }
                                    finally{
                                        cacheProxy.setMen(cacheLock + key, "0", DateTime.MaxValue);
                                    }
                                }
                            }
                            finally{
                                Monitor.Exit(myLock);
                            }
                        }
                    }
                }
                return cacheProxy.getMem(cacheProxy.prefix + key).AsTUnSafe<V>();
            }


            /// <summary>
            /// 强制更新缓存
            /// </summary>
            /// <param name="millseconds">等待的毫秒数</param>
            /// <param name="key">要删除的键</param>
            /// <returns></returns>
            public bool EnforceRefresh(int millseconds, K key){
                if (Monitor.TryEnter(myLock, millseconds)){
                    try{
                        lastGetTime = DateTime.MinValue;
                        cacheProxy.deleteMem(cacheProxy.prefix + key);
                        return true;
                    }
                    finally{
                        Monitor.Exit(myLock);
                    }
                }
                return false;
            }
        }

        #endregion
    }
}